package fileoper

import (
	"blog_go/internal/model"
	regexpattern "blog_go/pkg/common/regexppattern"
	"blog_go/pkg/helper/uuid"
	"bytes"
	"database/sql"
	"errors"
	"io"
	"mime/multipart"
	"net/url"
	"os"
	"path"
	"regexp"
	"strings"

	"github.com/PuerkitoBio/goquery"
	"github.com/microcosm-cc/bluemonday"
	"github.com/yuin/goldmark"
	"github.com/yuin/goldmark/extension"
	"github.com/yuin/goldmark/parser"
	"github.com/yuin/goldmark/renderer/html"
)

//const PublicPath = "../../"

// TODO 发布需修改
const PublicPath = ""

// 请求前缀
// TODO 发布需修改
// const strPrefix = "/"
const strPrefix = "/api/"

// 临时文件目录
const TempPath = "resources/temp"

// 永久文件目录
const PermanentPath = "resources/permanent"

// 文章图片存放路径
const ArticleImgPath = TempPath + "/img/article/"

// 视频存放路径
const ArticleVideoPath = TempPath + "/video/article/"

// 头像存放路径
const AvatarImgPath = PermanentPath + "/img/avatar/"

// 保存文件
func SaveFile(fileHeader *multipart.FileHeader, destination string) error {
	// 打开文件
	file, err := fileHeader.Open()
	if err != nil {
		return err
	}
	defer file.Close()

	// 创建目标文件
	destinationFile, err := os.Create(destination)
	if err != nil {
		return err
	}
	defer destinationFile.Close()

	// 将文件内容拷贝到目标文件
	_, err = io.Copy(destinationFile, file)
	if err != nil {
		return err
	}

	return nil
}

// 添加文件
func AddFileInfo(tx *sql.Tx, fileinfo *model.FileInfo, tempfile *multipart.FileHeader) int64 {

	ret, err := tx.Exec(`
		INSERT INTO fileinfo
		(
			file_type, 
			file_name, 
			file_use_to, 
			file_path
		) 
		values(?,?,?,?)`, fileinfo.FileType, fileinfo.FileName, fileinfo.FileUseTo, fileinfo.FilePath)

	if err != nil {
		return 0
	}
	fileId, err := ret.LastInsertId()

	if err != nil {
		return 0
	}

	if fileId < 0 {
		return 0
	}

	// common.PublicPath  解决 执行的文件不在根目录问题
	err = SaveFile(tempfile, PublicPath+fileinfo.FilePath)
	if err != nil {
		return 0
	}
	return fileId
}

// 更新文件
func UpdateFileInfo(tx *sql.Tx, fileinfo *model.FileInfo, tempfile *multipart.FileHeader) error {

	oldFilePath := ""

	tx.QueryRow("SELECT file_path FROM fileinfo WHERE file_id=? ", fileinfo.FileId).Scan(&oldFilePath)

	if oldFilePath == "" {
		return errors.New("查询不到原文件路径")
	}
	ret, err := tx.Exec(`
		UPDATE 
			fileinfo 
		SET 
			file_path = ?, 
			file_type = ?, 
			file_name = ? 
		WHERE 
			file_id = ?`, fileinfo.FilePath, path.Ext(tempfile.Filename), fileinfo.FileName, fileinfo.FileId)

	if err != nil {
		return err
	}
	fileId, err := ret.LastInsertId()

	if err != nil {
		return err
	}

	if fileId < 0 {
		return err
	}

	if fileExist(PublicPath + oldFilePath) {
		err = os.Remove(PublicPath + oldFilePath) //删除原图片
		if err != nil {
			return errors.New("删除旧文件错误")
		}
	}

	err = SaveFile(tempfile, PublicPath+fileinfo.FilePath) //保存新图片
	if err != nil {
		return errors.New("保存文件错误")
	}

	return nil
}

// 删除文件
func DelFileInfo(tx *sql.Tx, fileinfo *model.FileInfo) error {
	oldFilePath := ""
	tx.QueryRow("SELECT file_path FROM fileinfo where file_id=? ", fileinfo.FileId).Scan(&oldFilePath)

	if oldFilePath == "" {
		return errors.New("删除文件时文件路径错误")
	}

	ret, err := tx.Exec("DELETE FROM fileinfo WHERE file_id = ?", fileinfo.FileId)

	if err != nil {
		return err
	}
	fileId, err := ret.RowsAffected()

	if err != nil {
		return err
	}

	if fileId < 0 {
		return err
	}

	err = os.Remove(PublicPath + oldFilePath) //删除原图片
	if err != nil {
		return err
	}
	return nil
}

// 修改
func EditCover(tx *sql.Tx, files *multipart.Form, fileUseTo string, infileId int, filedist string) (int64, error) {

	var outfileid int64
	fileinfo := new(model.FileInfo)
	if len(files.File["files"]) == 1 && infileId == 0 { //上传了头像 且原先没有头像
		tempfile := files.File["files"][0]
		fileinfo.FileType = path.Ext(tempfile.Filename)
		fileinfo.FileName = uuid.GenUUID()
		fileinfo.FileUseTo = fileUseTo
		fileinfo.FilePath = filedist + fileinfo.FileName + path.Ext(tempfile.Filename)
		outfileid = AddFileInfo(tx, fileinfo, tempfile)

		if outfileid == 0 {
			return outfileid, errors.New("文件上传错误")
		}

	} else if len(files.File["files"]) == 1 && infileId > 0 { //上传了头像 且原先有头像 进行更改fileinfo

		outfileid = int64(infileId)

		tempfile := files.File["files"][0]
		fileinfo.FileId = infileId
		fileinfo.FileType = path.Ext(tempfile.Filename)
		fileinfo.FileName = uuid.GenUUID()
		fileinfo.FilePath = filedist + fileinfo.FileName + path.Ext(tempfile.Filename)
		err := UpdateFileInfo(tx, fileinfo, tempfile)

		if err != nil {
			return outfileid, err
		}
	} else if len(files.File["files"]) == 0 && infileId > 0 { //不上传了头像 且原先有头像 不删除

		return int64(infileId), nil

	} else if len(files.File["files"]) == 0 && infileId == 0 { // 原先没有头像  新传的也没有图片
		outfileid = 0
	}

	return outfileid, nil
}

// 删除富文本编辑器的文件
func DelHtmlResources(res string, tx *sql.Tx) bool {

	r := strings.NewReader(res)
	doc, err := goquery.NewDocumentFromReader(r)
	if err != nil {
		return false
	}

	flag := true
	doc.Find("img").Each(func(i int, s *goquery.Selection) {
		//解析<div>标签
		//h,err := s.Html()
		value, exist := s.Attr("src")
		value = strings.Replace(value, "\n", "", -1)
		if exist {
			myUrl, _ := url.Parse(value)
			params, _ := url.ParseQuery(myUrl.RawQuery)
			filepathtemp := params.Get("filename")
			if filepathtemp != "" {
				err = os.Remove(filepathtemp)
				if err != nil {
					flag = false
				}

				ret, err := tx.Exec(" DELETE FROM fileinfo where file_path = ?", filepathtemp)
				if err != nil {
					flag = false
				}

				n, err := ret.RowsAffected()
				if err != nil {
					flag = false
				}

				if n < 0 {
					flag = false
				}
			}
		}

	})

	if !flag {
		return flag
	}

	doc.Find("iframe").Each(func(i int, s *goquery.Selection) {
		//解析<div>标签
		//h,err := s.Html()
		value, exist := s.Attr("src")
		value = strings.Replace(value, "\n", "", -1)
		if exist {
			myUrl, _ := url.Parse(value)
			params, _ := url.ParseQuery(myUrl.RawQuery)
			filepathtemp := params.Get("filename")
			if filepathtemp != "" {
				err = os.Remove(filepathtemp)
				if err != nil {
					flag = false
				}

				ret, err := tx.Exec(" DELETE FROM fileinfo WHERE file_path = ?", filepathtemp)
				if err != nil {
					flag = false
				}

				n, err := ret.RowsAffected()
				if err != nil {
					flag = false
				}

				if n < 0 {
					flag = false
				}
			}
		}

	})
	if !flag {
		return flag
	}

	return true
}

// 替换文章内容的Temp
func ReplaceTempWithPermanent(path string) string {
	return strings.Replace(path, TempPath, PermanentPath, 1)
}

// 从临时目录移动文章资源
func UpdateTempResources(res string, tx *sql.Tx) (string, error) {
	var pathMap = make(map[string]string)

	// 使用正则表达式找到所有的src属性
	re := regexp.MustCompile(`src="([^"]+)"`)
	matches := re.FindAllStringSubmatch(res, -1)

	for _, match := range matches {
		originalSrc := match[1]
		myUrl, err := url.Parse(originalSrc)
		if err != nil {
			return "", err // 如果URL解析失败，返回错误
		}

		tempPath := strings.TrimPrefix(myUrl.Path, strPrefix)
		if tempPath != "" {
			permanentPath := ReplaceTempWithPermanent(tempPath)
			pathMap[tempPath] = permanentPath
			// 替换原始HTML字符串中的临时路径为永久路径
			res = strings.Replace(res, originalSrc, ReplaceTempWithPermanent(originalSrc), 1)
		}
	}

	// 更新文件系统和数据库记录
	for tempPath, permanentPath := range pathMap {
		if err := os.Rename(PublicPath+tempPath, PublicPath+permanentPath); err != nil {
			return "", err
		}
		if _, err := tx.Exec("UPDATE fileinfo SET file_path = ? WHERE file_path = ?", permanentPath, tempPath); err != nil {
			return "", err
		}
	}

	return res, nil // 返回更新后的HTML内容
}

// markdown转换成html，进行数据清洗
func ConvertMarkdownToSafeHTML(markdownContent string) (string, error) {
	md := goldmark.New(
		goldmark.WithExtensions(extension.GFM), // Using GitHub Flavored Markdown extensions
		goldmark.WithParserOptions(
			parser.WithAutoHeadingID(), // Automatically generate IDs for headings
		),
		goldmark.WithRendererOptions(
			html.WithXHTML(),  // Output XHTML instead of HTML5
			html.WithUnsafe(), // Allow raw HTML in markdown source
		),
	)

	// Buffer to hold the HTML output
	var buf bytes.Buffer

	// Convert markdown to HTML
	if err := md.Convert([]byte(markdownContent), &buf); err != nil {
		return "", err
	}

	// 使用bluemonday进行HTML清洗
	p := bluemonday.UGCPolicy()
	safeHTML := p.Sanitize(buf.String())

	return safeHTML, nil
}

// 修改文章里面的文件内容
func UpdateHtmlResources(res string, newres string, tx *sql.Tx) (bool, error) {

	var resarr []string    //原先的路径数组
	var newresarr []string //修改之后的路径数组

	//var removearr []string
	r := strings.NewReader(res)
	doc, err := goquery.NewDocumentFromReader(r)
	if err != nil {
		return false, err
	}

	doc.Find("img, iframe").Each(func(i int, s *goquery.Selection) {
		if value, exists := s.Attr("src"); exists {
			value = strings.Replace(value, "\n", "", -1)
			myUrl, _ := url.Parse(value)

			filepathtemp := strings.TrimPrefix(myUrl.Path, strPrefix)

			if filepathtemp != "" {
				resarr = append(resarr, filepathtemp)
			}
		}
	})

	newr := strings.NewReader(newres)
	newdoc, err := goquery.NewDocumentFromReader(newr)
	if err != nil {
		return false, err
	}

	newdoc.Find("img, iframe").Each(func(i int, s *goquery.Selection) {
		if value, exists := s.Attr("src"); exists {
			value = strings.Replace(value, "\n", "", -1)
			myUrl, _ := url.Parse(value)

			filepathtemp := strings.TrimPrefix(myUrl.Path, strPrefix)

			if filepathtemp != "" {
				newresarr = append(newresarr, filepathtemp)
			}
		}
	})

	// 将 newresarr 转换为集合
	newresSet := make(map[string]struct{})
	for _, newpath := range newresarr {
		newresSet[newpath] = struct{}{}
	}

	// 删除不再使用的资源
	for _, oldpath := range resarr {
		if _, found := newresSet[oldpath]; !found {
			ret, err := tx.Exec("DELETE FROM fileinfo WHERE file_path = ?", oldpath)
			if err != nil {
				return false, err
			}

			n, err := ret.RowsAffected()
			if err != nil {
				return false, err
			}

			if n <= 0 {
				return false, err
			}

			if fileExist(PublicPath + oldpath) {
				err = os.Remove(oldpath)
				if err != nil {
					return false, err
				}
			}
		}
	}

	return true, err
}

// 修改md内容
func UpdateMdContent(markdownContent string) string {
	// 正则表达式匹配Markdown中的图片链接
	regex := regexp.MustCompile(regexpattern.ArticleImgRegex)
	// 使用ReplaceAllStringFunc来更新所有匹配的路径
	return regex.ReplaceAllStringFunc(markdownContent, func(match string) string {
		matches := regex.FindStringSubmatch(match)
		if len(matches) < 2 {
			return match //
		}
		url := matches[1]
		// 替换路径前缀从'Temp'到'Permanent'
		newUrl := strings.Replace(url, TempPath, PermanentPath, 1)
		return match[:len(match)-len(url)-1] + newUrl + ")"
	})
}

func fileExist(filename string) bool {
	info, err := os.Stat(filename)
	if os.IsNotExist(err) {
		return false
	}

	return !info.IsDir()
}
